module obs_data_mod

  use flogger
  use datetime
  use fiona
  use namelist_mod
  use latlon_process_mod
  use latlon_mesh_mod
  use latlon_data_mod
  use obs_synop_mod
  use obs_raob_mod
  use obs_amdar_mod
  use obs_profiler_mod
  use obs_operators_mod

  implicit none

  private

  public obs_data_init
  public obs_data_final
  public obs_data_read
  public obs_data_write
  public obs_data_interp_prepare
  public obs_data_interp_model_data
  public obs_data_calc_mean_simulated_records
  public obs_data_calc_perb_simulated_records
  public obs_data_calc_innov
  public obs_synop_type
  public synop_record_type
  public obs_raob_type
  public raob_record_type
  public obs_amdar_type
  public amdar_record_type
  public obs_profiler_type
  public profiler_record_type
  public obs_synop
  public obs_raob
  public obs_amdar
  public obs_profiler

  type(obs_synop_type   ) obs_synop
  type(obs_raob_type    ) obs_raob
  type(obs_amdar_type   ) obs_amdar
  type(obs_profiler_type) obs_profiler

contains

  subroutine obs_data_init()

    call obs_data_final()

    call obs_synop   %init()
    call obs_raob    %init()
    call obs_amdar   %init()
    call obs_profiler%init()

  end subroutine obs_data_init

  subroutine obs_data_final()

  end subroutine obs_data_final

  subroutine obs_data_read(obs_start_time, obs_end_time)

    type(datetime_type), intent(in) :: obs_start_time
    type(datetime_type), intent(in) :: obs_end_time

    character(256) file_dir
    character(256) file_path
    integer ierr

    ! Read all of observation data.
    if (use_synop) call obs_synop%read(synop_files(1), mute=proc%id/=0)
    if (use_raob ) call obs_raob %read(raob_files (1), mute=proc%id/=0)
    if (use_amdar) call obs_amdar%read(amdar_files(1), mute=proc%id/=0)
    if (use_profiler) then
      !file_dir = profiler_files(1)
      !file_dir = file_dir(1:index(file_dir, '/', back=.true.))
      open(10, file=profiler_files(1), status='old')
      do
        read(10, '(A)', iostat=ierr) file_path
        if (ierr /= 0) exit
        call obs_profiler%read(trim(file_path), mute=proc%id/=0)
      end do
    end if

    ! Filter observation that are contained in the domain.
    call obs_synop%filter_by_latlon_and_time(proc%local_mesh%start_lon, proc%local_mesh%end_lon, proc%local_mesh%start_lat, proc%local_mesh%end_lat, obs_start_time, obs_end_time)
    call obs_raob %filter_by_latlon_and_time(proc%local_mesh%start_lon, proc%local_mesh%end_lon, proc%local_mesh%start_lat, proc%local_mesh%end_lat, obs_start_time, obs_end_time)
    call obs_amdar%filter_by_latlon_and_time(proc%local_mesh%start_lon, proc%local_mesh%end_lon, proc%local_mesh%start_lat, proc%local_mesh%end_lat, obs_start_time, obs_end_time)
    call obs_profiler%filter_by_latlon_and_time(proc%local_mesh%start_lon, proc%local_mesh%end_lon, proc%local_mesh%start_lat, proc%local_mesh%end_lat, obs_start_time, obs_end_time)

  end subroutine obs_data_read

  subroutine obs_data_write(file_path)

    character(*), intent(in) :: file_path

    character(8) key

    key = 'obs_w'

    call fiona_create_dataset(key, file_path=file_path, mpi_comm=proc%comm)
    call fiona_add_dim(key, 'ens', size=ensemble_size + 1, decomp=.true.) ! Include ensemble mean.
    call fiona_add_dim(key, 'sid_len', size=8)
    call obs_synop%init_write(key)
    call obs_raob %init_write(key)
    call obs_amdar%init_write(key)

    call fiona_start_output(key)
    call obs_synop%write(key)
    call obs_raob %write(key)
    call obs_amdar%write(key)
    call fiona_end_output(key)

  end subroutine obs_data_write

  subroutine obs_data_interp_prepare()

    if (proc%is_root_proc()) then
      call log_notice('Use observation operator to interpolate model data to observation sites.')
    end if
    call obs_operator_interp_prepare(obs_synop   , proc%local_mesh)
    call obs_operator_interp_prepare(obs_raob    , proc%local_mesh)
    call obs_operator_interp_prepare(obs_amdar   , proc%local_mesh)
    call obs_operator_interp_prepare(obs_profiler, proc%local_mesh)

  end subroutine obs_data_interp_prepare

  subroutine obs_data_interp_model_data(iens, model_data)

    integer, intent(in) :: iens
    type(latlon_data_type), intent(in) :: model_data

    call obs_operator_interp_run(proc%local_mesh, model_data, iens, obs_synop   )
    call obs_operator_interp_run(proc%local_mesh, model_data, iens, obs_raob    )
    call obs_operator_interp_run(proc%local_mesh, model_data, iens, obs_amdar   )
    call obs_operator_interp_run(proc%local_mesh, model_data, iens, obs_profiler)

  end subroutine obs_data_interp_model_data

  subroutine obs_data_calc_mean_simulated_records()

    call obs_synop   %calc_mean_simulated_records()
    call obs_raob    %calc_mean_simulated_records()
    call obs_amdar   %calc_mean_simulated_records()
    call obs_profiler%calc_mean_simulated_records()

  end subroutine obs_data_calc_mean_simulated_records

  subroutine obs_data_calc_perb_simulated_records()

    call obs_synop   %calc_perb_simulated_records()
    call obs_raob    %calc_perb_simulated_records()
    call obs_amdar   %calc_perb_simulated_records()
    call obs_profiler%calc_perb_simulated_records()

  end subroutine obs_data_calc_perb_simulated_records

  subroutine obs_data_calc_innov()

    call obs_synop   %calc_innov()
    call obs_raob    %calc_innov()
    call obs_amdar   %calc_innov()
    call obs_profiler%calc_innov()

  end subroutine obs_data_calc_innov

end module obs_data_mod
